<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>录音</title>
</head>
<body>
<button onclick="recOpen()" style="margin-right:10px">打开录音,请求权限</button>
<button onclick="recClose()" style="margin-right:0">关闭录音,释放资源</button>
<button id="start" onclick="recStart()">开始录制</button>
<button id="stop" onclick="recStop()">停止</button>
<button onclick="recPause()">暂停</button>
<button onclick="recResume()">继续</button>
<button onclick="recPlay()">播放</button>
<button onclick="recUpload()">上传</button>
<div class="pd recpower">
    <div style="height:40px;width:300px;background:#999;position:relative;">
        <div class="recpowerx" style="height:40px;background:#0B1;position:absolute;"></div>
        <div class="recpowert" style="padding-left:50px; line-height:40px; position: relative;"></div>
    </div>
</div>
</body>

<!-- 【1.1】引入核心文件 -->
<script src="/static/recorder/src/recorder-core.js"></script>

<!-- 【1.2】引入相应格式支持文件；如果需要多个格式支持，把这些格式的编码引擎js文件放到后面统统加载进来即可 -->
<script src="/static/recorder/src/engine/mp3.js"></script>
<script src="/static/recorder/src/engine/mp3-engine.js"></script>

<!-- 【1.3】引入可选的扩展支持项，如果不需要这些扩展功能可以不引入 -->
<script src="/static/recorder/src/extensions/frequency.histogram.view.js"></script>
<script src="/static/recorder/src/extensions/lib.fft.js"></script>


<!-- 【3】实现录音逻辑 -->
<script>
    var rec, wave, recBlob;

    /**调用open打开录音请求好录音权限**/
    function recOpen() {
        rec = null;
        wave = null;
        recBlob = null;
        var newRec = new Recorder({
            type: "mp3", sampleRate: 16000, bitRate: 16
            , onProcess: function (buffers, powerLevel, bufferDuration, bufferSampleRate, newBufferIdx, asyncEnd) {
                //录音实时回调，大约1秒调用12次本回调
                document.querySelector(".recpowerx").style.width = powerLevel + "%";
                document.querySelector(".recpowert").innerText = bufferDuration + " / " + powerLevel;

                //可视化图形绘制
                wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
            }
        });

        createDelayDialog();
        newRec.open(function () {
            dialogCancel();

            rec = newRec;

            //此处创建这些音频可视化图形绘制浏览器支持妥妥的
            wave = Recorder.FrequencyHistogramView({elem: ".recwave"});

            console.log("已打开录音，可以点击录制开始录音了", 2);
        }, function (msg, isUserNotAllow) {
            dialogCancel();
            console.log((isUserNotAllow ? "UserNotAllow，" : "") + "打开录音失败：" + msg, 1);
        });

        window.waitDialogClick = function () {
            dialogCancel();
            console.log("打开失败：权限请求被忽略，<span style='color:#f00'>用户主动点击的弹窗</span>", 1);
        };
    };


    /**关闭录音，释放资源**/
    function recClose() {
        if (rec) {
            rec.close();
            console.log("已关闭");
        } else {
            console.log("未打开录音", 1);
        }
        ;
    };


    /**开始录音**/
    function recStart() {
        if (rec && Recorder.IsOpen()) {
            recBlob = null;
            rec.start();
            console.log("已开始录音...");
        } else {
            console.log("未打开录音", 1);
        }
        ;
    };

    /**暂停录音**/
    function recPause() {
        if (rec && Recorder.IsOpen()) {
            rec.pause();
        } else {
            console.log("未打开录音", 1);
        }
        ;
    };

    /**恢复录音**/
    function recResume() {
        if (rec && Recorder.IsOpen()) {
            rec.resume();
        } else {
            console.log("未打开录音", 1);
        }
        ;
    };

    /**结束录音，得到音频文件**/
    function recStop() {
        if (!(rec && Recorder.IsOpen())) {
            console.log("未打开录音", 1);
            return;
        }
        ;
        rec.stop(function (blob, duration) {
            console.log(blob, (window.URL || webkitURL).createObjectURL(blob), "时长:" + duration + "ms");

            recBlob = blob;
            console.log("已录制mp3：" + duration + "ms " + blob.size + "字节，可以点击播放、上传了", 2);
        }, function (msg) {
            console.log("录音失败:" + msg, 1);
        });
    };


    /**播放**/
    function recPlay() {
        if (!recBlob) {
            console.log("请先录音，然后停止后再播放", 1);
            return;
        }
        ;
        var cls = ("a" + Math.random()).replace(".", "");
        console.log('播放中: <span class="' + cls + '"></span>');
        var audio = document.createElement("audio");
        audio.controls = true;
        document.querySelector("." + cls).appendChild(audio);
        //简单利用URL生成播放地址，注意不用了时需要revokeObjectURL，否则霸占内存
        audio.src = (window.URL || webkitURL).createObjectURL(recBlob);
        audio.play();

        setTimeout(function () {
            (window.URL || webkitURL).revokeObjectURL(audio.src);
        }, 5000);
    };

    /**上传**/
    function recUpload() {
        var blob = recBlob;
        if (!blob) {
            console.log("请先录音，然后停止后再上传", 1);
            return;
        }
        ;

        //本例子假设使用原始XMLHttpRequest请求方式，实际使用中自行调整为自己的请求方式
        //录音结束时拿到了blob文件对象，可以用FileReader读取出内容，或者用FormData上传
        var api = "https://xx.xx/test_request";
        var onreadystatechange = function (title) {
            return function () {
                if (xhr.readyState == 4) {
                    if (xhr.status == 200) {
                        console.log(title + "上传成功", 2);
                    } else {
                        console.log(title + "没有完成上传，演示上传地址无需关注上传结果，只要浏览器控制台内Network面板内看到的请求数据结构是预期的就ok了。", "#d8c1a0");

                        console.error(title + "上传失败", xhr.status, xhr.responseText);
                    }
                    ;
                }
                ;
            };
        };
        console.log("开始上传到" + api + "，请求稍后...");

        /***方式一：将blob文件转成base64纯文本编码，使用普通application/x-www-form-urlencoded表单上传***/
        var reader = new FileReader();
        reader.onloadend = function () {
            var postData = "";
            postData += "mime=" + encodeURIComponent(blob.type);//告诉后端，这个录音是什么格式的，可能前后端都固定的mp3可以不用写
            postData += "&upfile_b64=" + encodeURIComponent((/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result) || [])[1]) //录音文件内容，后端进行base64解码成二进制
            //...其他表单参数

            var xhr = new XMLHttpRequest();
            xhr.open("POST", api);
            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            xhr.onreadystatechange = onreadystatechange("上传方式一【Base64】");
            xhr.send(postData);
        };
        reader.readAsDataURL(blob);

        /***方式二：使用FormData用multipart/form-data表单上传文件***/
        var form = new FormData();
        form.append("upfile", blob, "recorder.mp3"); //和普通form表单并无二致，后端接收到upfile参数的文件，文件名为recorder.mp3
        //...其他表单参数

        var xhr = new XMLHttpRequest();
        xhr.open("POST", api);
        xhr.onreadystatechange = onreadystatechange("上传方式二【FormData】");
        xhr.send(form);
    };


    //recOpen我们可以选择性的弹一个对话框：为了防止移动端浏览器存在第三种情况：用户忽略，并且（或者国产系统UC系）浏览器没有任何回调
    var showDialog = function () {
        if (!/mobile/i.test(navigator.userAgent)) {
            return;//只在移动端开启没有权限请求的检测
        };
        dialogCancel();

        //显示弹框，应该使用自己的弹框方式
        var div = document.createElement("div");
        document.body.appendChild(div);
        div.innerHTML = (''
            + '<div class="waitDialog" style="z-index:99999;width:100%;height:100%;top:0;left:0;position:fixed;background:rgba(0,0,0,0.3);">'
            + '<div style="display:flex;height:100%;align-items:center;">'
            + '<div style="flex:1;"></div>'
            + '<div style="width:240px;background:#fff;padding:15px 20px;border-radius: 10px;">'
            + '<div style="padding-bottom:10px;">录音功能需要麦克风权限，请允许；如果未看到任何请求，请点击忽略~</div>'
            + '<div style="text-align:center;"><a onclick="waitDialogClick()" style="color:#0B1">忽略</a></div>'
            + '</div>'
            + '<div style="flex:1;"></div>'
            + '</div>'
            + '</div>');
    };
    var createDelayDialog = function () {
        dialogInt = setTimeout(function () {
            showDialog();
        }, 8000);
    };
    var dialogInt;
    var dialogCancel = function () {
        clearTimeout(dialogInt);

        //关闭弹框，应该使用自己的弹框方式
        var elems = document.querySelectorAll(".waitDialog");
        for (var i = 0; i < elems.length; i++) {
            elems[i].parentNode.removeChild(elems[i]);
        }
        ;
    };
    //recOpen弹框End
</script>

</html>
